﻿// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved
// Developer:  Michael Antonio
using System;
using System.Linq;
using System.Collections.Generic;

namespace MichMan.Utilities.Requests
{
    [AttributeUsage(AttributeTargets.Class)]
    public class CacheableAttribute : Attribute
    {
        public CacheableAttribute(string policy)
            : this(policy, null)
        {
        }

        public CacheableAttribute(string policy, TimeSpan? expires)
        {
            Policy = policy;
            Expires = expires;
        }

        public string Policy { get; private set; }
        public TimeSpan? Expires { get; private set; }
        public ICache GetCache()
        {
            return CacheManager.Instance.GetCache(Policy);
        }
    }

    /// <summary>
    /// Inherit from this to handle new policies.  Set the CacheManager.Instance before using the CacheManager for the first time.
    /// </summary>
    public class CacheManager
    {
        protected CacheManager()
        {
            AddPolicy("NoCache", typeof(NoCache));
            AddPolicy("Local", typeof(LocalCache));
            AddPolicy("ThreadLocal", typeof(ThreadLocalCache));
        }

        public static CacheManager Instance
        {
            get
            {
                return Singleton<CacheManager>.Create(() => new CacheManager());
            }
            set
            {
                if (Singleton<CacheManager>.Get() != null)
                {
                    throw new InvalidOperationException("CacheManager can only be set once per process.");
                }
                Singleton<CacheManager>.Set(value);
            }
        }

        public void AddPolicy(string policy, Type cacheType)
        {
            if (String.IsNullOrEmpty(policy))
            {
                return;
            }

            if (cacheType == null && _cachePolicies.ContainsKey(policy))
            {
                _cachePolicies.Remove(policy);
            }

            if (!cacheType.IsInstanceOfType(typeof(ICache)))
            {
                return;
            }

            _cachePolicies[policy] = cacheType;
        }

        private Dictionary<string, Type> _cachePolicies = new Dictionary<string,Type>();
        /// <summary>
        /// List of policies supported by the cache manager.
        /// </summary>
        public virtual IEnumerable<string> Policies
        {
            get
            {
                return Enumerable.AsEnumerable(_cachePolicies.Keys);
            }
        }

        /// <summary>
        /// Returns the cache associated with the policy, or NoCache if there is none.
        /// </summary>
        /// <param name="policy"></param>
        /// <returns></returns>
        public virtual ICache GetCache(string policy)
        {
            if (_cachePolicies.ContainsKey(policy))
            {
                Type cacheType = _cachePolicies[policy];
                return (ICache)Activator.CreateInstance(cacheType);
            }
            return Singleton<NoCache>.Create(() => new NoCache());
        }

        /// <summary>
        /// Clears all cached data from all caches.
        /// </summary>
        public virtual void ClearAll()
        {
            foreach (string policy in Policies)
            {
                GetCache(policy).Clear();
            }
        }

        /// <summary>
        /// True if the object type has the CachableAttribute applied to it.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static bool IsCacheable(Type type)
        {
            return CacheableAttribute(type) != null;
        }

        private static CacheableAttribute CacheableAttribute(Type type)
        {
            return type.GetCustomAttributes(true).Where(a => typeof(CacheableAttribute).IsAssignableFrom(a.GetType())).Cast<CacheableAttribute>().FirstOrDefault();
        }

        /// <summary>
        /// Stores the representation of an object of type <paramref name="type"/> in the appropriate cache
        /// </summary>
        /// <param name="type">Type of object being cached.</param>
        /// <param name="key">The key to use when caching the object.</param>
        /// <param name="representation">The representation of the object to store.</param>
        /// <returns></returns>
        public static bool AddToCache(Type type, string key, object representation)
        {
            CacheableAttribute attr = CacheableAttribute(type);
            if (attr == null)
            {
                return false;
            }

            return attr.GetCache().Insert(key, representation, attr.Expires);
        }

        /// <summary>
        /// Returns the cached representation of the object of type <paramref name="type"/>.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <param name="key"></param>
        /// <returns></returns>
        public static T RetrieveFromCache<T>(Type type, string key)
        {
            return RetrieveFromCache(type, key, default(T));
        }

        public static T RetrieveFromCache<T>(Type type, string key, T defaultValue)
        {
            CacheableAttribute attr = CacheableAttribute(type);
            return (T)RetrieveFromCache(attr, key, defaultValue);
        }

        public static object RetrieveFromCache(CacheableAttribute attr, string key, object defaultValue)
        {
            if (attr == null)
            {
                return defaultValue;
            }

            return attr.GetCache().Get(key, defaultValue);
        }
    }
}